<template>
  <el-form ref="ruleForm" :model="state.form">
    <el-table
      style="width: 100%"
      v-bind="$attrs"
      :data="state.form.list"
      :border="border"
      :row-key="rowKey"
    >
      <template v-for="item in columns" :key="item.prop">
        <!-- selection || index -->
        <el-table-column
          :key="item.prop ?? item.type"
          v-bind="item"
          :align="item.align ?? 'center'"
          :width="item.width ?? '55'"
          :reserve-selection="item.type == 'selection'"
          v-if="item.type == 'selection' || item.type == 'index'"
        >
        </el-table-column>
        <!-- expand -->
        <el-table-column
          :key="item.prop ?? item.type"
          v-bind="item"
          :align="item.align ?? 'center'"
          :width="item.width ?? '80'"
          v-if="item.type == 'expand'"
          v-slot="scope"
        >
          <slot :name="item.type" v-bind="scope"></slot>
        </el-table-column>
        <el-table-column
          v-if="!item.type && item.prop && !item.hidden"
          v-bind="item"
          :key="item.prop"
          :column-key="item.prop"
          :prop="item.prop"
          :label="item.label"
          :width="item.width"
          :min-width="item.minWidth"
          :fixed="item.fixed"
          :header-align="item.headerAlign"
          :sortable="item.sortable"
          :show-overflow-tooltip="(item.showOverflowTooltip === false || item.prop === COLUMN_OPERATE_PROP) ? false : true"
        >
          <template #default="{ row, $index }">
            <!-- 编辑行 -->
            <template v-if="(row.isEdit || row.isAdd) && getFormItem(item).valueType">
              <el-form-item
                :prop="`list.${$index}.${item.prop}`"
                :rules="getFormItem(item).rules"
                style="margin-bottom: 14px"
              >
                <component :is="`el-${getFormItem(item).valueType}`" v-model="row[getFormItem(item).prop]">
                  <template v-if="getFormItem(item).valueType === 'select'">
                    <el-option
                      v-for="op in item.options"
                      :key="op.value"
                      :label="op.label"
                      :value="op.value"
                    ></el-option>
                  </template>
                </component>
              </el-form-item>
            </template>
            <template v-else>
              <template v-if="item.prop === COLUMN_OPERATE_PROP">
                <div v-if="row.isEdit || row.isAdd" style="margin-bottom: 14px">
                  <el-button
                    size="small"
                    type="primary"
                    :link="true"
                    @click="saveData(row, $index)"
                  >
                    保存
                  </el-button>
                  <el-button
                    size="small"
                    type="primary"
                    :link="true"
                    @click="handleCancel(row, $index)"
                  >
                    取消
                  </el-button>
                </div>
                <template v-else>
                  <slot :name="item.prop" :row="row" :index="$index" :item="getFormItem(item)">
                    <el-button
                      size="small"
                      type="primary"
                      :link="true"
                      @click="editData(row)"
                    >
                      编辑
                    </el-button>
                    <el-button
                      size="small"
                      type="primary"
                      :link="true"
                      @click="deleteData($index)"
                    >
                      删除
                    </el-button>
                  </slot>
                </template>
              </template>
              <template v-else>
                <slot :name="item.prop" :row="row" :index="$index" :item="item">
                  {{ renderCellData(item, row, $index) }}
                </slot>
              </template>
            </template>
          </template>
        </el-table-column>
      </template>
        <!-- 插入表格最后一行之后的插槽 -->
        <template #append>
          <slot name="append"> </slot>
        </template>
        <!-- 表格无数据情况 -->
        <template #empty>
          <div class="table-empty">
            <slot name="empty">
              <!-- <img src="@/assets/images/notData.png" alt="notData" /> -->
              <div>暂无数据</div>
            </slot>
          </div>
        </template>
    </el-table>

    <!-- 分页组件 -->
    <Pagination
      v-if="pagination"
      :pageData="filterState.pagData"
      :handle-size-change="handleSizeChange"
      :handle-current-change="handleCurrentChange"
    />
  </el-form>
</template>

<script lang="ts">
export default {
  name: "EditTable",
  inheritAttrs: false,
};
</script>
<script setup lang="ts">
import { EditTableProps } from '@/types/table';
import {
  ElTable,
  ElMessage,
  ElMessageBox,
} from "element-plus";
import Pagination from './Pagination.vue'
import { COLUMN_OPERATE_PROP } from "../utils";

// 接受父组件参数，配置默认值
const props = withDefaults(defineProps<EditTableProps>(), {
  columns: () => [],
  requestAuto: true,
  pagination: true,
  params: {},
  border: true,
  rowKey: "id",
});

interface RowProps {
  isAdd?: boolean
  isEdit?: boolean
  [key: string]: any
}

interface CacheRowsProps {
  [key: string]: RowProps
}

const state = reactive<{
  form: {
    list: RowProps[]
  },
  cacheRows: CacheRowsProps
}>({
  form: {
    list: [],
  },
  // 缓存编辑前数据
  cacheRows: {},
});

function getFormItem(item: any) {
  const { label, prop, form, options } = item
  const newItem = {
    label,
    prop,
    ...form,
  }
  return options ? { options, ...newItem } : newItem
}

function renderCellData(item: any, row: any, index: number) {
  if (typeof item.customRender === "function") {
    return item.customRender(row, index);
  }
  const cellValue = row[item.prop];
  const list = item.options;
  if (list) {
    return list.find((item: any) => item.value === cellValue)?.label;
  }
  return cellValue ?? "-";
}

function isAddOrEdit() {
  return state.form.list.some((v) => v.isEdit || v.isAdd);
}

const ruleForm = ref();
// 校验表单
function validate(...args: any[]) {
  return ruleForm.value.validate(...args);
}
// 校验表单
function clearValidate() {
  return ruleForm.value.clearValidate();
}

// 回显数据
// @arg Array
function setData(formList: RowProps[]) {
  if (!formList || !formList.length) {
    state.form.list = [];
    return;
  }
  const list = formList.map((rowItem) => ({
    ...rowItem,
    isEdit: false,
    isAdd: false,
  }));
  Object.assign(state.form, { list });
  // 重置缓存表格行
  state.cacheRows = {};
  nextTick(() => {
    clearValidate();
  });
}

// 获取数据
function getData() {
  return state.form.list.map(item => {
    const { isEdit, isAdd, ...rest } = item;
    return rest;
  });
}

// 添加数据
// @arg row
function addData(row: RowProps = {}) {
  // 判断是否正在编辑状态或添加状态
  if (isAddOrEdit()) {
    ElMessage.warning("请先保存或取消正在编辑行");
    return;
  }
  const formItem = props.columns
    .filter((v) => v.prop !== COLUMN_OPERATE_PROP)
    .reduce(
      (r, c) => {
        const item = getFormItem(c)
        return {
          ...r,
          [item.prop]: item.valueType === "switch" ? false : "",
        }
      },
      {
        isAdd: true,
        isEdit: false,
      }
    );
  const obj: any = {
    ...formItem,
    ...row,
  };
  const { rowKey } = props;
  obj[rowKey] = row[rowKey] || Date.now();
  state.form.list.push(obj);
  const key = obj[rowKey];
  state.cacheRows[key] = JSON.parse(JSON.stringify(obj));
}

// 点击编辑,进入编辑状态
// @arg row
function editData(row: RowProps) {
  row.isEdit = true;
  const key = row[props.rowKey];
  state.cacheRows[key] = JSON.parse(JSON.stringify(row));
}

// 删除数据
// @arg index
function deleteData(idx: number) {
  // 删除缓存数据
  const key = state.form.list[idx][props.rowKey];
  delete state.cacheRows[key];
  // 删除表格数据
  state.form.list.splice(idx, 1);
  ruleForm.value.fields?.forEach((n: any) => {
    if (n.prop.split(".")[1] === idx) {
      n.clearValidate();
    }
  });
}

// 保存数据
async function saveData(row: RowProps, idx: number) {
  const ret = Object.keys(row)
    .map((r) => `list.${idx}.${r}`)
    .filter((r) => !/^(isEdit|isAdd)/g.test(r));
  ruleForm.value.validateField(ret, (isValid: boolean) => {
    if (isValid) {
      row.isEdit = false;
      row.isAdd = false;
      state.form.list[idx] = row;
    }
  });
}

// 点击取消
function handleCancel(row: RowProps, index: number) {
  if (row.isEdit) {
    const key = row[props.rowKey];
    // 取消编辑,还原数据
    const value = JSON.parse(JSON.stringify(state.cacheRows[key]));
    value.isEdit = false;
    value.isAdd = false;
    state.form.list[index] = value;
  } else if (row.isAdd) {
    ElMessageBox.confirm("此操作将不会保存编辑数据, 是否继续?", "提示", {
      type: "warning",
    })
      .then(() => {
        deleteData(index);
      })
      .catch(() => {});
  }
}

// 加载状态
const loading = ref(false);
function setLoading(value: boolean) {
  loading.value = value;
}

// 查询条件
const filterState = reactive({
  pagData: {
    pageNum: 1,
    pageSize: 10,
    total: 0,
  },
});
function setPag(value: any) {
  filterState.pagData = {
    ...filterState.pagData,
    ...value,
  };
}
// 获取请求参数
function getRequestParam() {
  const { pagData } = filterState;
  const { pageNum, pageSize } = pagData;
  return {
    pageNum,
    pageSize,
    ...props.params,
  };
}
// 查询表格
async function handleRequest() {
  if (typeof props.requestApi !== "function") return;
  setLoading(true);
  const reqParams = getRequestParam();
  const res = await props.requestApi(reqParams);
  if (res) {
    const { list = [], total } = res;
    setData(list);
    setPag({ total });
  }
  setLoading(false);
}

// 初始化请求
onMounted(() => {
  if (props.requestAuto) {
    handleRequest();
  }
});

// 监听传入参数变化，重新获取表格数据
watch(() => props.params, handleRequest, { deep: true });

function onChange(pag: any) {
  // 判断是否正在编辑状态或添加状态
  if (isAddOrEdit()) {
    ElMessage.warning("请先保存或取消正在编辑行");
    return;
  }
  setPag(pag);
  handleRequest();
}
function handleCurrentChange(pageNum: number) {
  onChange({ pageNum });
}

function handleSizeChange(pageSize: number) {
  const pag = {
    pageNum: 1,
    pageSize,
  };
  onChange(pag);
}

// 刷新表格
function refresh() {
  handleRequest();
}

// 暴露给父组件的参数和方法(外部需要什么，都可以从这里暴露出去)
defineExpose({
  setData,
  getData,
  addData,
  editData,
  deleteData,
  refresh,
  validate,
});
</script>

<style lang="scss" scoped>
</style>